#include "PopupMessage.hpp"
#include "PopupAbstractMessage.hpp"
#include <string.h>
#include "PopupUtilities.hpp"
#include <PopupLoggerUI.hpp>

using namespace Popup;
using namespace PopupUtils;

bool MessageNetworker::receive(Message **p_message,
                               RawMessage **p_rawMessage,
                               unsigned int *p_rawMessageSize)
{
  RawMessageHeader _header;
  Message *_message = 0;

  RawMessageHeader_initialize(&_header);

  // If we are given a non-null pointer, its up to us to free
  // the memory
  if (*p_message != 0) {
    delete *p_message;
  }
  if (p_rawMessage != 0 && *p_rawMessage != 0) {
    delete *p_rawMessage;
  }

  // Read next message header
  bool _rc = socket->receiveBytes(&_header);
  if (!_rc)
  {
    error("Oops! recv() failed.");
  }

  if (_rc == true) {

    RawMessageHeader_ntoh(&_header);

    // Make sure this is a popup message
    if (_header.magic != POPUP_MAGIC)
    {
      error("Received something which is not from Popup world "
            "(expected magic=%x received=%x)", POPUP_MAGIC, _header.magic);
      _rc = false;
    }
  }

  if (_rc == true) {

    size_t _messageSize = _header.size;
    unsigned char *_buffer = new unsigned char[_messageSize];

    _rc = socket->receiveBytes(_buffer, _header.size);
    //Utils::dumpBuffer(_buffer, _header.size);

    // Duplicate original message in case we're asked for!
    if (p_rawMessage != 0) {
      *p_rawMessageSize = _header.size + sizeof(RawMessageHeader);
      *p_rawMessage = (RawMessage*) new unsigned char[*p_rawMessageSize];
      // Copy header (restore endianness if necessary)
      (*p_rawMessage)->header = _header;
      RawMessageHeader_hton(&(*p_rawMessage)->header);
      // Copy message
      memcpy(&(*p_rawMessage)->message, _buffer, _messageSize);
    }

    switch (_header.encryption) {

      case POPUP_ENCRYPTION_NONE:
        _message = (Message*) _buffer;
        break;

      case POPUP_ENCRYPTION_BLOWFISH:
        _rc = sslContext->blowfishDecrypt((unsigned char*) _buffer, _messageSize);
        _message = (Message*) _buffer;
        break;

      case POPUP_ENCRYPTION_RSA: {
        // Final message needs to be in another chunk of memory
        _message = (Message*) new unsigned char[_messageSize];
        _rc = sslContext->rsaDecrypt((unsigned char*)_message,
                                     &_messageSize,
                                     (const unsigned char*)_buffer,
                                     _messageSize);
        delete _buffer;
        break;
      }
      default:
        error("Not cool!");
        break;
    }
  }

  if (_rc) {
    Message_ntoh(_message);
    *p_message = _message;
  }

  return _rc;
}

bool MessageNetworker::send(AbstractMessage *p_message,
                                 const SslContext *p_sslContext)
{
  if (p_message->encryption == POPUP_ENCRYPTION_RSA)
  {
    return const_send(p_message, p_sslContext);
  }
  else if (p_message->prepareSend())
  {
    size_t _utilSize = p_message->getUtilMessageSize();
    RawMessage *_message = p_message->fullMessage;

    if (p_message->encryption == POPUP_ENCRYPTION_BLOWFISH) {
      const SslContext *_sslContext =
          (p_sslContext != 0)? p_sslContext : sslContext;
      _sslContext->blowfishEncrypt((unsigned char*) p_message->message,
                                   _utilSize);
    }

    RawMessageHeader *_header = (RawMessageHeader*) _message;
    _header->magic      = POPUP_MAGIC;
    _header->encryption = p_message->encryption;
    _header->size       = _utilSize;
    RawMessageHeader_hton(_header);

    return socket->sendBytes((unsigned char*) _message, _utilSize +
                      sizeof(RawMessageHeader));
    //Utils::dumpBuffer(_message, _utilSize + sizeof(PopupProtocolMsgHeader));
  }
  return false;
}

bool MessageNetworker::const_send(AbstractMessage *p_message,
                                       const SslContext *p_sslContext)
{
  bool _rc = false;

  if (p_message->prepareSend())
  {
    RawMessage *_message = p_message->fullMessage;
    const SslContext *_sslContext =
                (p_sslContext != 0)? p_sslContext : sslContext;
    unsigned char *_buffer = 0;
    size_t _utilSize = p_message->getUtilMessageSize();
    size_t _rsaCypherGrowSize = 0;

    switch (p_message->encryption)
    {
      case POPUP_ENCRYPTION_NONE:
        break;

      case POPUP_ENCRYPTION_BLOWFISH:
        _buffer = new unsigned char[_utilSize];
        _sslContext->blowfishEncrypt(&_buffer[sizeof(RawMessageHeader)],
                                     (const unsigned char*) p_message->message,
                                     _utilSize);
        _message = (RawMessage*) _buffer;
        break;

      case POPUP_ENCRYPTION_RSA:
        // We need a buffer which is bigger that original message
        _buffer = new unsigned char[sizeof(RawMessageHeader) +
                                    (2 * _utilSize)];
        size_t _encryptedSize = 0;
        _sslContext->rsaEncrypt(&_buffer[sizeof(RawMessageHeader)],
                                &_encryptedSize,
                                (const unsigned char*) p_message->message,
                                _utilSize);
        _message  = (RawMessage*) _buffer;
        _rsaCypherGrowSize = _encryptedSize - _utilSize;
        break;
    }

    RawMessageHeader *_header = (RawMessageHeader*) _message;
    _header->magic      = POPUP_MAGIC;
    _header->encryption = p_message->encryption;
    _header->size       = _utilSize + _rsaCypherGrowSize;
    RawMessageHeader_hton(_header);

    _rc = socket->sendBytes((unsigned char*) _message,
                      p_message->getTotalMessageSize() +
                      _rsaCypherGrowSize);

    if (_buffer != 0) {
      delete _buffer;
    }
    return _rc;
  }
  return _rc;
}

bool MessageNetworker::send(const RawMessage *p_message,
                                 unsigned int p_size)
{
  return socket->sendBytes((const unsigned char*) p_message, p_size);
}

//============================================================================
// Base types init, endianness and dump
//============================================================================

void Popup::RawMessageHeader_initialize(RawMessageHeader *p_header)
{
  p_header->magic      = POPUP_MAGIC;
  p_header->encryption = POPUP_ENCRYPTION_NONE;
  p_header->size       = 0;
}

void Popup::RawMessageHeader_ntoh(RawMessageHeader *p_header)
{
  p_header->magic      = ntohs(p_header->magic);
  p_header->encryption = ntohs(p_header->encryption);
  p_header->size       = ntohl(p_header->size);
}

void Popup::RawMessageHeader_hton(RawMessageHeader *p_header)
{
  p_header->magic      = htons(p_header->magic);
  p_header->encryption = htons(p_header->encryption);
  p_header->size       = htonl(p_header->size);
}

void Popup::RawMessageHeader_ntoh(RawMessageHeader & p_dest,
                                  const RawMessageHeader & p_src)
{
  p_dest.magic      = ntohs(p_src.magic);
  p_dest.encryption = ntohs(p_src.encryption);
  p_dest.size       = ntohl(p_src.size);
}

void Popup::RawMessageHeader_hton(RawMessageHeader & p_dest,
                                  const RawMessageHeader & p_src)
{
  p_dest.magic      = htons(p_src.magic);
  p_dest.encryption = htons(p_src.encryption);
  p_dest.size       = htonl(p_src.size);
}

void Popup::MessageHeader_initialize(MessageHeader *p_header)
{
  p_header->type        = POPUP_MSG_TYPE_UNK;
  p_header->payloadSize  = 0;
}

void Popup::MessageHeader_ntoh(MessageHeader *p_header)
{
  p_header->type       = ntohs(p_header->type);
  p_header->payloadSize = ntohl(p_header->payloadSize);
}

void Popup::MessageHeader_hton(MessageHeader *p_header)
{
  p_header->type       = htons(p_header->type);
  p_header->payloadSize = htonl(p_header->payloadSize);
}

void Popup::ntohll(unsigned long long *value) {
  if (ntohs(0x1234) != 0x1234) {
    *value =
        ((*value & 0x00000000000000FFULL) << 56) |
        ((*value & 0x000000000000FF00ULL) << 40) |
        ((*value & 0x0000000000FF0000ULL) << 24) |
        ((*value & 0x00000000FF000000ULL) << 8)  |
        ((*value & 0x000000FF00000000ULL) >> 8)  |
        ((*value & 0x0000FF0000000000ULL) >> 24) |
        ((*value & 0x00FF000000000000ULL) >> 40) |
        ((*value & 0xFF00000000000000ULL) >> 56);
  }
}

void Popup::htonll(unsigned long long *value) {
  if (htons(0x1234) != 0x1234) {
    *value =
        ((*value & 0x00000000000000FFULL) << 56) |
        ((*value & 0x000000000000FF00ULL) << 40) |
        ((*value & 0x0000000000FF0000ULL) << 24) |
        ((*value & 0x00000000FF000000ULL) << 8)  |
        ((*value & 0x000000FF00000000ULL) >> 8)  |
        ((*value & 0x0000FF0000000000ULL) >> 24) |
        ((*value & 0x00FF000000000000ULL) >> 40) |
        ((*value & 0xFF00000000000000ULL) >> 56);
  }
}

//-------------------------------------------------------

void Popup::MessageField_initialize(MessageField *p_field)
{
  p_field->id   = 0x0000;
  p_field->type = POPUP_FIELD_DATA_TYPE_UNK;
  p_field->size = 0;

}

void Popup::MessageField_ntoh(MessageField *p_field)
{
  size_t _offset = 0;

  p_field->id   = ntohs(p_field->id);
  p_field->size = ntohl(p_field->size);
  p_field->type = ntohs(p_field->type);

  switch (p_field->type) {
    case POPUP_FIELD_DATA_TYPE_SHORT:
      for (_offset = 0; _offset < p_field->size; _offset += sizeof(unsigned short)) {
        unsigned short *_val = (unsigned short *) &p_field->data[_offset];
        *_val = ntohs(*_val);
      }
      break;
    case POPUP_FIELD_DATA_TYPE_LONG:
      for (_offset = 0; _offset < p_field->size; _offset += sizeof(unsigned int)) {
        unsigned int *_val = (unsigned int *) &p_field->data[_offset];
        *_val = ntohl(*_val);
      }
      break;
    case POPUP_FIELD_DATA_TYPE_LONG_LONG:
      for (_offset = 0; _offset < p_field->size; _offset += sizeof(unsigned long long)) {
        unsigned long long *_val = (unsigned long long *) &p_field->data[_offset];
        ntohll(_val);
      }
      break;
    case POPUP_FIELD_DATA_TYPE_UNK:
    case POPUP_FIELD_DATA_TYPE_STRING:
    case POPUP_FIELD_DATA_TYPE_BOOLEAN:
    default:
      break;
  }
}

void Popup::MessageField_hton(MessageField *p_field)
{
  size_t _offset = 0;

  switch (p_field->type) {
    case POPUP_FIELD_DATA_TYPE_SHORT:
      for (_offset = 0; _offset < p_field->size; _offset += sizeof(unsigned short)) {
        unsigned short *_val = (unsigned short *) &p_field->data[_offset];
        *_val = htons(*_val);
      }
      break;
    case POPUP_FIELD_DATA_TYPE_LONG:
      for (_offset = 0; _offset < p_field->size; _offset += sizeof(unsigned int)) {
        unsigned int *_val = (unsigned int *) &p_field->data[_offset];
        *_val = htonl(*_val);
      }
      break;
    case POPUP_FIELD_DATA_TYPE_LONG_LONG:
      for (_offset = 0; _offset < p_field->size; _offset += sizeof(unsigned long long)) {
        unsigned long long *_val = (unsigned long long *) &p_field->data[_offset];
        htonll(_val);
      }
      break;
    case POPUP_FIELD_DATA_TYPE_STRING:
    case POPUP_FIELD_DATA_TYPE_UNK:
    case POPUP_FIELD_DATA_TYPE_BOOLEAN:
    default:
      break;
  }

  p_field->id   = htons(p_field->id);
  p_field->size = htonl(p_field->size);
  p_field->type = htons(p_field->type);
}

void Popup::MessageField_dump(MessageField *p_field)
{
  size_t _offset = 0;

  printf("BEGIN --> Dumping field (address=%p)\n"
         "  id   : 0x%hX\n"
         "  size : %u bytes\n",
         p_field, p_field->id, p_field->size);
  switch(p_field->type) {
    case POPUP_FIELD_DATA_TYPE_SHORT:
      printf("  type : short\n");
      for (_offset = 0; _offset < p_field->size; _offset += sizeof(unsigned short)) {
        unsigned short *val = (unsigned short*) &p_field->data[_offset];
        printf("  data : %hu\n", *val);
      }
      break;

    case POPUP_FIELD_DATA_TYPE_LONG:
      printf("  type : long\n");
      printf("  data : ");
      for (_offset = 0; _offset < p_field->size; _offset += sizeof(unsigned int)) {
        unsigned int *val = (unsigned int*) &p_field->data[_offset];
        printf("  data : %u\n", *val);
      }
      printf("\n");
      break;

    case POPUP_FIELD_DATA_TYPE_LONG_LONG:
      printf("  type : long long\n");
      for (_offset = 0; _offset < p_field->size; _offset += sizeof(unsigned long long)) {
        unsigned long long *val = (unsigned long long*) &p_field->data[_offset];
#ifdef _WIN32
        printf("  data : %lX\n", *val);
#else
        printf("  data : %llX\n", *val);
#endif
      }
      break;

    case POPUP_FIELD_DATA_TYPE_STRING:
      printf("  type : string\n");
      printf("  data : %s\n", p_field->data);
      break;

    case POPUP_FIELD_DATA_TYPE_BOOLEAN:
      printf("  type : boolean\n");
      printf("  data : %s\n", (p_field->data[0] == 0x01? "true" : "false"));
      break;

    case POPUP_FIELD_DATA_TYPE_UNK:
    default:
      printf("  type : unk (%0xhX)\n", p_field->type);
      printf("  data :\n");
      Popup::Utils::dumpBuffer(&p_field->data[0], p_field->size);
      break;
  }
  printf("<--------- END\n");
}

//-------------------------------------------------------

void Popup::Message_initialize(Message *p_msg)
{
  MessageHeader_initialize(&p_msg->header);
}

void Popup::Message_ntoh(Message *p_msg)
{
  MessageHeader_ntoh(&p_msg->header);
}

void Popup::Message_hton(Message *p_msg)
{
  MessageHeader_hton(&p_msg->header);
}
